package dht.simulation;

import java.util.HashMap;

public class RouteTableV2 implements Cloneable {
	public int serverNum;
	public HashMap<Integer, VNodeV2> vnodeId2Vnode;
	public HashMap<Integer, Integer> serverId2Version;
	public static final int ringLength = GlobalVariables.ringLength;

	public RouteTableV2(int serverNum) {
		this.serverNum = serverNum;
		vnodeId2Vnode = new HashMap<Integer, VNodeV2>();
		serverId2Version = new HashMap<Integer, Integer>();
		int vnodeId = 0;
		for (int i = 1; i <= serverNum; ++i) {
			int remainder = (i <= (ringLength % serverNum) ? 1 : 0);
			for (int j = 0; j < ringLength / serverNum + remainder; ++j) {
				vnodeId2Vnode.put(vnodeId, new VNodeV2(vnodeId, i, 0.0));
				++vnodeId;
			}
		}
		for (int i = 1; i <= serverNum; ++i) {
			serverId2Version.put(i, 0);
		}
	}

	public int lookup(String fileName) {
		int vnodeId = hashCode(fileName);
		return vnodeId2Vnode.get(vnodeId).serverId;
	}

	public void increaseReadCounter(String fileName, int value) {
		int vnodeId = hashCode(fileName);
		VNodeV2 vnode = vnodeId2Vnode.get(vnodeId);
		vnode.readCounter += value;
	}

	public void increaseWriteCounter(String fileName, int value) {
		int vnodeId = hashCode(fileName);
		VNodeV2 vnode = vnodeId2Vnode.get(vnodeId);
		vnode.writeCounter += value;
	}

	public void updateVNodeStatistics(int serverId, double lastReportTime,
			HashMap<Integer, VNodeV2> vtable) throws DhtException {
		for (Integer vnodeId : vtable.keySet()) {
			VNodeV2 vnode = vtable.get(vnodeId);
			if (vnode.serverId == serverId) {
				if (vnodeId2Vnode.get(vnodeId).serverId == serverId) {
					VNodeV2 newVnode = (VNodeV2) vnode.clone();
					newVnode.lastReportTime = lastReportTime;
					vnodeId2Vnode.put(vnodeId, newVnode);
				} else {
					// throw new DhtException(
					// "updateVNodeStatistics info don't match, serverId: "
					// + serverId);
				}
			}
		}
	}

	public HashMap<Integer, Double> getServerLoads() {
		HashMap<Integer, Double> serverId2Load = new HashMap<Integer, Double>();
		for (int i = 1; i <= serverNum; ++i) {
			serverId2Load.put(i, 0.0);
		}
		for (VNodeV2 vnode : vnodeId2Vnode.values()) {
			serverId2Load.put(vnode.serverId, serverId2Load.get(vnode.serverId)
					+ vnode.getLoad());
		}
		return serverId2Load;
	}

	public HashMap<Integer, Integer> getServerVnodeNum() {
		HashMap<Integer, Integer> serverId2VnodeNum = new HashMap<Integer, Integer>();
		for (int i = 1; i <= serverNum; ++i) {
			serverId2VnodeNum.put(i, 0);
		}
		for (VNodeV2 vnode : vnodeId2Vnode.values()) {
			serverId2VnodeNum.put(vnode.serverId,
					serverId2VnodeNum.get(vnode.serverId) + 1);
		}
		return serverId2VnodeNum;
	}

	public void increaseVersion(int serverId) {
		serverId2Version.put(serverId, serverId2Version.get(serverId) + 1);
	}

	public void dump() {
		int totalRead = 0, totalWrite = 0;
		for (Integer vnodeId : vnodeId2Vnode.keySet()) {
			totalRead += vnodeId2Vnode.get(vnodeId).readCounter;
			totalWrite += vnodeId2Vnode.get(vnodeId).writeCounter;
		}
		System.out.println(totalRead + " " + totalWrite);
	}

	public synchronized Object clone() {
		try {
			RouteTableV2 o = (RouteTableV2) super.clone();
			if (vnodeId2Vnode != null) {
				o.vnodeId2Vnode = new HashMap<Integer, VNodeV2>();
				for (Integer vnodeId : vnodeId2Vnode.keySet()) {
					o.vnodeId2Vnode.put(vnodeId,
							(VNodeV2) vnodeId2Vnode.get(vnodeId).clone());
				}
			}
			if (serverId2Version != null) {
				o.serverId2Version = new HashMap<Integer, Integer>();
				for (Integer serverId : serverId2Version.keySet()) {
					o.serverId2Version.put(serverId,
							serverId2Version.get(serverId));
				}
			}
			return o;
		} catch (CloneNotSupportedException e) {
			throw new InternalError();
		}
	}

	public static int hashCode(String fileName) {
		return Math.abs(fileName.hashCode()) % ringLength;
	}

}
